// * mixin 是一个类，其方法可被其他类使用，而无需继承。
// * 换句话说，mixin 提供了实现特定行为的方法，但是我们不单独使用它，而是使用它来将这些行为添加到其他类中。

// * 一个 Mixin 实例：

// mixin
let sayHiMixin = {
	sayHi() {
		console.log(`Hello ${this.name}`)
	},

	sayBye() {
		console.log(`Bye ${this.name}`)
	},
}

// 使用 mixin
class User {
	constructor(name) {
		this.name = name
	}
}

// 拷贝方法（将 sayHiMixin 对象中的方法合并到 User.prototype 中 ）
Object.assign(User.prototype, sayHiMixin)

const user = new User('John')

user.sayHi() // Hello John
user.sayBye() // Bye John

// ********************************************************************************************************************

// * EventMixin

let eventMixin = {
	/**
	 * 订阅事件，用法：
	 * menu.on('select', function(item) { ... })
	 */
	on(eventName, handler) {
		if (!this._eventHandlers) this._eventHandlers = {}
		if (!this._eventHandlers[eventName]) {
			this._eventHandlers[eventName] = []
		}
		this._eventHandlers[eventName].push(handler)
	},

	/**
	 * 取消订阅，用法：
	 * menu.off('select', handler)
	 */
	off(eventName, handler) {
		let handlers = this._eventHandlers?.[eventName]
		if (!handlers) return
		for (let i = 0; i < handlers.length; i++) {
			if (handlers[i] === handler) {
				handlers.splice(i--, 1)
			}
		}
	},

	/**
	 * 生成具有给定名称和数据的事件
	 * this.trigger('select', data1, data2)
	 */
	trigger(eventName, ...args) {
		if (!this._eventHandlers?.[eventName]) {
			return // 该事件名称没有对应的事件处理程序（handler）
		}

		// 调用事件处理程序（handler）
		this._eventHandlers[eventName].forEach((handler) => {
			handler.apply(this, args)
		})
	},
}

// * 用法：
// 创建一个 class
class Menu {
	choose(value) {
		this.trigger('select', value)
	}
}

// 添加带有事件相关方法的 mixin
Object.assign(Menu.prototype, eventMixin)

let menu = new Menu()

// 添加一个事件处理程序（handler），在被选择时被调用：
menu.on('select', (value) => console.log(`被选中的值1： ${value}`))
menu.on('select', (value) => console.log(`被选中的值2： ${value}`))
menu.on('select', (value) => console.log(`被选中的值3： ${value}`))

// 触发事件 => 运行上述的事件处理程序（handler）并显示：
// 被选中的值：123
menu.choose('123') // 被选中的值1： 123。  被选中的值2： 123。  被选中的值3： 123。

// ********************************************************************************************************************

// * 总结:
// * 一些其它编程语言允许多重继承。JavaScript 不支持多重继承，但是可以通过将方法拷贝到原型中来实现 mixin。
